from itertools import count
from tkinter import SE
import uuid
import requests
import re
import time
import hashlib
import json
from logs.loger import imaotai_log
import yaml
import datetime
import geocoder
import math
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
import jwt

class I_Maotai_Rob:
    
    def __init__(self):

        # 配置信息
        # self.config = self.get_config()

        # 手机，用来收验证码
        self.PHONE_NUMBER = "18726917230"

        # 设备id(非必填)
        self.DEVICE_ID = None

        # 设备经纬度
        self.latlng = []


        
        # 所在省份
        self.PROVINCE_NAME = "江苏省"

        # 城市
        self.CITY_NAME = "南京市"

        # 店铺名称
        self.SHOP_NAME = None
        # 店铺id 
        self.SHOP_ID = None
        # 店铺缺货模式，可选值为NEAREST（距离最近）或INVENTORY（库存最多）。设置该值时，需要同时设置 PROVINCE_NAME 和 CITY_NAME
        self.SHOP_MODE = 'INVENTORY'
        # 地址
        self.SHOP_FULLADDRESS = None
        # 店铺经纬度
        self.SHOP_LAT = None
        self.SHOP_LNG = None
        # 商铺里的商品列表
        self.SHOP_PROVINCE = []
        # 商铺距离我的距离
        self.SHOP_DISTANCE = None

        # 预约商品列表
        self.PRODUCT_LIST = []

        # sessionId
        self.sessionId = None
    
        # 加密值 
        self.SALT = None
        self.ENCRYPT_KEY = None
        self.ENCRYPT_IV = None

        # 版本号
        self.MT_VERSION = None

        self.filtered_data = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtdCIsImV4cCI6MTc0Njc3Nzk5NCwidXNlcklkIjoxMTI5Mzk0Nzk0LCJkZXZpY2VJZCI6IjU5ZDE5ZTg0LWYyODgtNDM5Mi1iMmI4LTljZmZiZmU1MzQxYSIsImlhdCI6MTc0NDE4NTk5NH0.ej0Bs7WrZYUUu0-tsjjo3enOA0vOQwKj8hEb7WWuf-Y"
    def _load_config_from_db(self,config_list):
        """ 从数据库加载配置 """
        config_dict = {item['Name']: item['Value'] for item in config_list}
        self.SALT = config_dict.get('SALT')
        self.ENCRYPT_KEY = config_dict.get('ENCRYPT_KEY')
        self.ENCRYPT_IV = config_dict.get('ENCRYPT_IV')
        self.MT_VERSION = config_dict.get('MT_VERSION')
        self.sessionId = config_dict.get('SESSIONID')
   
    
    def get_user_latlng(self):
        """ 通过ip获取当前用户的经纬度 """

        # 通过 IP 获取位置信息
        g = geocoder.ip("me",language='ZH')  # 'me' 表示当前设备

        if g.ok:
            self.latlng = g.latlng # [纬度, 经度]
            imaotai_log.info(f"定位成功: {self.latlng}")
        else:
            imaotai_log.error("定位失败")


    def signature(self,content, time):
        """ 加密方法 """
        # 计算 md5 值
        text = self.SALT + content + str(time)
        md5 = hashlib.md5(text.encode()).hexdigest()
        
        return md5

    def get_device_id(self,device_id=None):
        """获取设备id"""
        if not device_id:
            self.DEVICE_ID =  str(uuid.uuid4())
            # 修改配置文件
            imaotai_log.info(f"生成新的设备id:{self.DEVICE_ID}")
        else:
            self.DEVICE_ID = device_id
            imaotai_log.info(f"使用配置文件设备id:{self.DEVICE_ID}")
        
        return self.DEVICE_ID
    
    def get_mt_version(self):
        """获取版本号"""
        try:
            url = "https://apps.apple.com/cn/app/i%E8%8C%85%E5%8F%B0/id1600482450"
            with requests.get(url) as response:
                response.encoding = 'utf-8'  # 确保使用正确的编码
                html_content = response.text

            # 使用正则表达式匹配版本号
            pattern = re.compile(r'new__latest__version">(.*?)</p>', re.DOTALL)
            match = pattern.search(html_content)

            if match:
                mt_version = match.group(1).strip().replace('版本 ', '')  # 去掉前后的空白字符
                imaotai_log.info(f"版本号:{mt_version}")

        except Exception as e:
            imaotai_log.info(f"版本号获取异常:{e}")
        
        return mt_version 


    def send_code(self,mobile, device_id):
        """ 获取验证码 """
        cur_time = int(time.time() * 1000)
        data = {
            "mobile": mobile,
            "md5": self.signature(mobile, cur_time),
            "timestamp": str(cur_time)
        }
        headers = {
            "MT-Device-ID": device_id,
            "MT-APP-Version": self.MT_VERSION,
            "User-Agent": "iOS;16.3;Apple;?unrecognized?",
            "Content-Type": "application/json"
        }
        url = "https://app.moutai519.com.cn/xhr/front/user/register/vcode"
        response = requests.post(url, headers=headers, data=json.dumps(data))
        json_response = response.json()

        if json_response.get("code") == 2000:
            imaotai_log.info(f"「发送验证码返回」：{json.dumps(json_response)}")
            return "验证码发送成功"
        else:
            imaotai_log.error(f"「发送验证码-失败」：{json.dumps(json_response)}")
            return json_response.get('message')

    def login(self,mobile, code, device_id):
        """ 登录 """
        try:
            cur_time = int(time.time() * 1000)
            data = {
                "mobile": mobile,
                "vCode": code,
                "md5": self.signature(mobile + code, cur_time),
                "timestamp": str(cur_time),
                "MT-APP-Version": self.MT_VERSION
            }
            headers = {
                "MT-Device-ID": device_id,
                "MT-APP-Version": self.MT_VERSION,
                "User-Agent": "iOS;16.3;Apple;?unrecognized?",
                "Content-Type": "application/json"
            }
            url = "https://app.moutai519.com.cn/xhr/front/user/register/login"
            response = requests.post(url, headers=headers, data=json.dumps(data))
            json_response = response.json()

            # 输出登录请求的返回结果
            imaotai_log.info(f"「登录请求返回」：{json.dumps(json_response)}")

            return json_response

        except Exception as e:
            imaotai_log.error(f"登录异常:{e}")
        
        return False
    

    def get_shop_info(self,province_name, city_name):
        """ 获取售卖商铺信息 """
        # 第一步：获取myserviceshops的URL
        api_url = "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get"
        response = requests.get(api_url)
        data = response.json()

        if data["code"] != 2000:
            raise Exception("获取资源信息失败")

        myserviceshops_url = data["data"]["myserviceshops"]["url"]

        # 第二步：下载并解析myserviceshops.json
        response = requests.get(myserviceshops_url)
        shops_data = response.json()

        # 第三步：根据provinceName和cityName过滤数据
        result = []
        for _, shop_info in shops_data.items():
            if shop_info["provinceName"] == province_name and shop_info[
                    "cityName"] == city_name:
                result.append({
                    "lat": shop_info["lat"],
                    "lng": shop_info["lng"],
                    "name": shop_info["name"],
                    "shopId": shop_info["shopId"],
                    "fullAddress": shop_info["fullAddress"],
                    "cityName": shop_info["cityName"],
                    "provinceName": shop_info["provinceName"]
                })

        return result
    
    def get_item_info(self):
        """ 获取可预约的商品信息 """
        # 生成时间戳
        timestamp = str(int(time.mktime(datetime.date.today().timetuple())) * 1000)

        # 发送请求
        api_url = f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/{timestamp}"
        response = requests.get(api_url)
        data = response.json()

        if data["code"] != 2000:
            raise Exception("获取商品信息失败")

        # 解析响应
        sessionId = data["data"]["sessionId"]
        # 提取itemCode和title
        item_list = data["data"]["itemList"]
        result = [{
            "itemCode": item["itemCode"],
            "title": item.get("title", f"未知商品，可结合该商品图片链接来判断：{item.get('pictureV2', '无图片信息')}，同时到 APP 核实。")
        } for item in item_list]

        return {"sessionId": sessionId, "itemList": result}


    def get_shop_by_product_id(self,province_name, product_id,product_info):
        """
            获取指定商品可以预约的店铺信息
            province_name - 所在省份
            product_id - 需要预约的商品id
        """

        timestamp_today = str(int(time.mktime(datetime.date.today().timetuple())) * 1000)

        api_url = f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/shop/list/slim/v3/{self.sessionId}/{province_name}/{product_id}/{timestamp_today}"
        response = requests.get(api_url)

        if 404 == response.status_code:
            imaotai_log.info(f"🚫 请求的资源未找到（404错误），请求的城市：{province_name}，商品ID：{product_id}，请检查这两个值是否正确。")
        
        data = response.json()

        if data["code"] != 2000:
            imaotai_log.info("🚫 获取指定商品可以预约的店铺信息失败")
        for shop in data["data"]["shops"]:
            for item in shop["items"]:
                if item["itemId"] == product_id:
                    product_info.append({
                        "shopId": shop["shopId"],
                        "inventory": item["inventory"],
                        "itemId":product_id,
                    })
    

    # 获取两个地点之间的距离
    def haversine(self,lat1, lng1, lat2, lng2):
        # 将经纬度转换为弧度
        lat1, lng1, lat2, lng2 = map(math.radians, [lat1, lng1, lat2, lng2])

        # Haversine 公式
        dlat = lat2 - lat1
        dlng = lng2 - lng1
        a = math.sin(
            dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlng / 2)**2
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

        # 地球半径（公里）
        R = 6371.0
        distance = R * c
        # 保留三位小数
        return round(distance, 3)

    def filter_well_stocked_shops(self,shops, min_item_inventory=1, min_total_inventory=20):
        """
        筛选出所有商品都有库存且总库存充足的店铺
        
        参数:
        shops: 店铺列表
        min_item_inventory: 每个商品的最小库存量（默认至少1件）
        min_total_inventory: 店铺总库存的最小阈值
        
        返回:
        符合条件且按总库存降序排列的店铺列表
        """
        well_stocked_shops = []
        
        for shop in shops:
            # 跳过没有库存信息的店铺
            if not shop.get('province', []):
                continue
                
            total_inventory = 0
            all_items_in_stock = True
            
            # 检查每个商品的库存
            for item in shop['province']:
                inventory = item.get('inventory', 0)
                if inventory < min_item_inventory:
                    all_items_in_stock = False
                    break
                total_inventory += inventory

            # 如果所有商品都有库存且总库存达标
            if all_items_in_stock and total_inventory >= min_total_inventory:

                well_stocked_shops.append({
                    'shop': shop,
                    'total_inventory': total_inventory
                })
        
        # 按总库存降序排序
        well_stocked_shops.sort(key=lambda x: x['total_inventory'], reverse=True)
        
        # 返回原始店铺数据（去掉中间计算结果）
        return [item['shop'] for item in well_stocked_shops]




    def get_max_inventory_shops(self,data):
        """获取 每个商品库存最多的店铺信息"""

        max_inventory_shops = []  # 最终返回的列表
        item_id_to_max_info = {}  # 临时存储每种商品的最大库存信息

        for shop in data:
            if not shop.get('province'):  # 跳过没有库存的店铺
                continue
            
            for product in shop['province']:
                item_id = product['itemId']
                current_inventory = product['inventory']
                
                # 如果该商品尚未记录，或者当前库存更大，则更新
                if (item_id not in item_id_to_max_info) or (current_inventory > item_id_to_max_info[item_id]['inventory']):
                    item_id_to_max_info[item_id] = {
                        'itemId': item_id,
                        'inventory': current_inventory,
                        'name': shop['name'],
                        'shopId': shop['shopId'],
                        'fullAddress': shop['fullAddress'],
                        'lat': shop['lat'],
                        'lng': shop['lng'],
                        'distance':shop['distance'],
                        'fullAddress':shop['fullAddress']
                    }
        
        # 将字典转换为列表
        max_inventory_shops = list(item_id_to_max_info.values())
        return max_inventory_shops
    def aes_cbc_encrypt(self,data, key, iv):
        """ 加密 """
        key = str(key)  # 将key转为字符串
        iv = str(iv)    # 将iv转为字符串

        cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
        padded_data = pad(data.encode('utf-8'), AES.block_size)
        encrypted_data = cipher.encrypt(padded_data)
        return base64.b64encode(encrypted_data).decode('utf-8')

    def reserve_product(self,itemId, shopId, imaotai_user_id, token, deviceId, lat, lng):
        """ 预约商品 """
        mt_k = f'{int(time.time() * 1000)}'
        headers = {
            'User-Agent': 'iOS;16.3;Apple;?unrecognized?',
            'MT-Token': token,
            'MT-Network-Type': 'WIFI',
            'MT-User-Tag': '0',
            'MT-K': mt_k,
            'MT-Info': '028e7f96f6369cafe1d105579c5b9377',
            'MT-APP-Version': self.MT_VERSION,
            'Accept-Language': 'zh-Hans-CN;q=1',
            'MT-Device-ID': deviceId,
            'MT-Bundle-ID': 'com.moutai.mall',
            'MT-Lng': str(lng),
            'MT-Lat': str(lat),
            'Content-Type': 'application/json',
            'userId': str(imaotai_user_id)
        }
        requestBody = {
            "itemInfoList": [{
                "count": 1,
                "itemId": str(itemId)
            }],
            "sessionId": self.sessionId,
            "userId": str(imaotai_user_id),
            "shopId": str(shopId)
        }
        actParam = self.aes_cbc_encrypt(json.dumps(requestBody), self.ENCRYPT_KEY,
                                self.ENCRYPT_IV)
        requestBody['actParam'] = actParam
        response = requests.post(
            'https://app.moutai519.com.cn/xhr/front/mall/reservation/add',
            headers=headers,
            json=requestBody)
        code = response.json().get('code', 0)
        if code == 2000:
            result = response.json().get('data', {}).get('successDesc', "未知")
            imaotai_log.info(f"商品ID {itemId} ✅ 预约成功: {result}")
            return f"商品ID {itemId} ✅ 预约成功: {result}"
        elif code == 4820:
            message = response.json().get('data', {}).get('updateDesc', "API 可能限制了 APP 版本，可以尝试重新生成环境变量")
            error_msg = f'🚫 预约失败: 错误码 {code}, 错误信息: {message}'
            imaotai_log.error(f"商品ID {itemId} {error_msg}")
            return f"商品ID {itemId} {error_msg}"
        else:
            message = response.json().get("message", "未知原因")
            error_msg = f'🚫 预约失败: 错误码 {code}, 错误信息: {message}'
            imaotai_log.error(f"商品ID {itemId} {error_msg}")
            return f"商品ID {itemId} {error_msg}"
            # 如果 message 包含 "请选择另外的门店申购"，则根据店铺缺货模式获取可预约的店铺 ID


    def check_reservation_result(self,token, device_id):
        """ 查询申购结果 """
        url = f"https://app.moutai519.com.cn/xhr/front/mall/reservation/list/pageOne/queryV2"
        mt_k = f'{int(time.time() * 1000)}'
        headers = {
            "User-Agent": "iOS;16.3;Apple;?unrecognized?",
            "MT-Device-ID": device_id,
            "MT-APP-Version": self.MT_VERSION,
            'MT-Token': token,
            'MT-Network-Type': 'WIFI',
            'MT-User-Tag': '0',
            'MT-K': mt_k,
            'MT-Bundle-ID': 'com.moutai.mall',
            'MT-R': 'clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw==',
            'MT-SN': 'clips_ehwpSC0fLBggRnJAdxYgFiAYLxl9Si5PfEl/TC0afkw='
        }

        response = requests.get(url, headers=headers)
        resultData = json.loads(response.text)
        resultCode = resultData.get("code")
        if resultCode != 2000:
            message = resultData.get("message")
            imaotai_log.info(f"({resultCode}){message}")

        # 处理预约结果
        reservations = resultData.get("data", {}).get("reservationItemVOS", [])
        if not reservations:
            imaotai_log.info("🚫 暂无申购记录")
            return False

        # 获取当天日期
        today = datetime.datetime.now().date()
        today_str = today.strftime("%Y-%m-%d")
        imaotai_log.info(f"📅 今天的日期是: {today_str}")
        result = []
        for item in reservations:
            # 获取预约时间
            reservation_time = datetime.datetime.fromtimestamp(
                item.get("reservationTime") / 1000).date()
            # 筛选今天预约的商品
            if reservation_time == today:
                status_text = {
                    0: "⌛️ 静候申购结果",
                    1: "❌ 申购失败",
                    2: "🎉 申购成功"
                }.get(item.get("status"), "未知状态")

                session_name = f"[{item.get('sessionName', '')}]" if item.get(
                    'sessionName') else ""
                item_name = item.get("itemName", "")
                item_id = item.get("itemId", "")

                # 输出结果
                imaotai_log.info(
                    f"🍺 {session_name}[{item_id}] {item_name}：{status_text}。")
                result.append({'item_id':item_id,'item_name':item_name,'status_text':status_text})

        return result


    
    def check_jwt(self,jwt_value):
        """ 检查 JWT 有效期 """
        # 解码 JWT
        try:
            # 注意：此处的密钥应与生成 JWT 时使用的密钥一致
            decoded = jwt.decode(jwt_value, options={"verify_signature": False})

            # 获取 exp 时间戳
            exp_timestamp = decoded.get("exp")
            if exp_timestamp:
                # 转换为日期
                exp_date = datetime.datetime.fromtimestamp(
                    exp_timestamp, tz=datetime.timezone.utc)

                # 获取当前时间
                current_date = datetime.datetime.now(datetime.timezone.utc)

                exp_date_str = exp_date.strftime('%Y-%m-%d %H:%M:%S')

                # 判断是否过期
                if current_date > exp_date:
                    imaotai_log.info(
                        f"⚠️ TOKEN 已过期: {exp_date_str}，请重新执行 第1、2步 脚本获取最新 TOKEN、COOKIE 值。"
                    )
                else:
                    imaotai_log.info(f"✅ TOKEN 有效: 过期时间为 {exp_date_str}")
                
                return exp_date_str
            else:
                imaotai_log.warning("⚠️ TOKEN 中没有 'exp' 字段")
        except jwt.DecodeError:
            imaotai_log.error("⚠️ TOKEN 解析失败")
        
        return False

    def main(self):
        # 检查 TOKEN 有效期
        self.check_jwt(self.filtered_data['token'])
        self.get_user_latlng()
        self.get_device_id()
        self.get_mt_version()

        # if self.send_code(self.PHONE_NUMBER, self.DEVICE_ID, self.MT_VERSION):
        #     imaotai_log.info("验证码发送成功,请注意查收")

        # CODE = input("请输入验证码: ")
        # if self.login(self.PHONE_NUMBER, CODE, self.DEVICE_ID, self.MT_VERSION):
        #     imaotai_log.info("登录成功")


        # 获取当前省份当前地区的商店信息
        shop_info = self.get_shop_info(self.PROVINCE_NAME, self.CITY_NAME)

        # 获取可预约的商品信息
        result = self.get_item_info()
        self.sessionId = result['sessionId']
        imaotai_log.info(f"获取到 SessionId(可以理解为申购活动批次，每天都会变化，一般+1): {result['sessionId']}")

        for item in result['itemList']:
            imaotai_log.info(f"可预约商品,itemCode: {item['itemCode']}, title: {item['title']}")
            # 获取的是当前省份所有商铺里所有商品可预约的数据
            self.get_shop_by_product_id(self.PROVINCE_NAME, item['itemCode'],item['title'])

        # 将当前省份当前地区的商店信息 与 所有商铺里所有商品可预约的数据 进行整合处理
        for i in shop_info:
            data = []
            for j in self.PRODUCT_LIST:
                if i['shopId'] == j['shopId']:
                    data.append({'inventory':j['inventory'],'itemId':j['itemId'],'title':j['title']})
            i['province'] = data

            # 计算距离
            distance = self.haversine(float(self.latlng[0]), float(self.latlng[1]), float(i["lat"]),float(i["lng"]))
            i['distance'] = distance
            
        
        imaotai_log.info(f"「商铺信息输出-开始」,{self.PROVINCE_NAME} - {self.CITY_NAME}")

        for shop in shop_info:
            imaotai_log.info("-------------------------")
            imaotai_log.info(f"店铺名称: {shop['name']}")
            imaotai_log.info(f"店铺ID: {shop['shopId']}")
            imaotai_log.info(f"地址: {shop['fullAddress']}")
            imaotai_log.info(f"纬度: {shop['lat']}")
            imaotai_log.info(f"经度: {shop['lng']}")
            imaotai_log.info(f"距离: {shop['distance']} 公里")
            for i in shop['province']:
                imaotai_log.info(f"商品名称: {i['title']}")
                imaotai_log.info(f"商品ID: {i['itemId']}")
                imaotai_log.info(f"商品库存数量: {i['inventory']}")

            imaotai_log.info("-------------------------")
        imaotai_log.info(f"「商铺信息输出-结束」,总数: {len(shop_info)}")
        imaotai_log.info(shop_info)

    
        if self.SHOP_MODE == "NEAREST":
            imaotai_log.info("选择商铺模式:NEAREST（距离最近）")
            # 计算用户位置到店铺的距离，并且按照距离近到远排序，把距离添加到 filter_shops 中
            shop_info.sort(key=lambda x: x["distance"])

        elif self.SHOP_MODE == "INVENTORY":
            imaotai_log.info("选择商铺模式：INVENTORY（库存最多）")
            shop_info = self.filter_well_stocked_shops(shop_info)
        

        self.SHOP_NAME = shop_info[0]['name']
        self.SHOP_ID = shop_info[0]['shopId']
        self.SHOP_FULLADDRESS = shop_info[0]['fullAddress']
        self.SHOP_LAT = shop_info[0]['lat']
        self.SHOP_LNG = shop_info[0]['lng']
        self.SHOP_PROVINCE = shop_info[0]['province']
        self.SHOP_DISTANCE = shop_info[0]['distance']
        
        imaotai_log.info(f"「选择-商店信息」,店铺名称：{self.SHOP_NAME},店铺ID: {self.SHOP_ID},地址: {self.SHOP_FULLADDRESS},纬度: {self.SHOP_LAT},维度: {self.SHOP_LNG},商品信息:{self.SHOP_PROVINCE},距离：{self.SHOP_DISTANCE} 公里")
        for i in self.SHOP_PROVINCE:
            imaotai_log.info(f"开始预约:{i['title']}")
            # 预约商品
            self.reserve_product(
                        itemId=i['itemId'],
                        shopId=self.SHOP_ID,
                        sessionId=self.sessionId,
                        userId=self.filtered_data['userId'],
                        token=self.filtered_data['token'],
                        deviceId=self.DEVICE_ID,
                        mtVersion=self.MT_VERSION,
                        lat=self.latlng[0],
                        lng=self.latlng[1]
                    )
        

    def text(self):
        # 检查 TOKEN 有效期
        # self.check_jwt(self.filtered_data['token'])
                # 获取申购结果
        self.check_reservation_result(token = self.filtered_data,device_id="59d19e84-f288-4392-b2b8-9cffbfe5341a",mt_version="1.8.0")
        

        

# I_Maotai_Rob().main()
# I_Maotai_Rob().text()